/*
 ===========================================================================
 Copyright (C) 1999-2005 Id Software, Inc.
 Copyright (C) 2000-2006 Tim Angus

 This file is part of Tremulous.

 Tremulous is free software; you can redistribute it
 and/or modify it under the terms of the GNU General Public License as
 published by the Free Software Foundation; either version 2 of the License,
 or (at your option) any later version.

 Tremulous is distributed in the hope that it will be
 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Tremulous; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 ===========================================================================
 */

// g_weapon.c
// perform the server side effects of a weapon firing

#include "g_local.h"

static vec3_t forward, right, up;
static vec3_t muzzle;

/*
 ================
 G_ForceWeaponChange
 ================
 */
void
G_ForceWeaponChange(gentity_t *ent, weapon_t weapon)
{
  int i;

  if (ent)
  {
    ent->client->ps.pm_flags |= PMF_WEAPON_SWITCH;

    if (weapon == WP_NONE || !BG_InventoryContainsWeapon(weapon, ent->client->ps.stats))
    {
      //switch to the first non pistol weapon
      for(i = WP_NONE + 1;i < WP_NUM_WEAPONS;i++)
      {
        if (i == WP_PISTOL)
          continue;

        if (BG_InventoryContainsWeapon(i, ent->client->ps.stats))
        {
          ent->client->ps.persistant[PERS_NEWWEAPON] = i;
          break;
        }
      }

      //only got the pistol to switch to
      if (i == WP_NUM_WEAPONS)
        ent->client->ps.persistant[PERS_NEWWEAPON] = WP_PISTOL;
    }
    else
      ent->client->ps.persistant[PERS_NEWWEAPON] = weapon;

    // Lak: The following hack has been moved to PM_BeginWeaponChange, but I'm going to
    // redundantly leave it here as well just in case there's a case I'm forgetting
    // because I don't want to face the gameplay consequences such an error would have

    // force this here to prevent flamer effect from continuing 
    ent->client->ps.generic1 = WPM_NOTFIRING;

    ent->client->ps.weapon = ent->client->ps.persistant[PERS_NEWWEAPON];
  }
}

/*
 =================
 G_GiveClientMaxAmmo
 =================
 */
void
G_GiveClientMaxAmmo(gentity_t *ent, qboolean buyingEnergyAmmo)
{
  int i;
  int maxAmmo, maxClips;
  qboolean weaponType, restoredAmmo = qfalse;

  for(i = WP_NONE + 1;i < WP_NUM_WEAPONS;i++)
  {
    if (buyingEnergyAmmo)
      weaponType = BG_FindUsesEnergyForWeapon(i);
    else
      weaponType = !BG_FindUsesEnergyForWeapon(i);

    if (BG_InventoryContainsWeapon(i, ent->client->ps.stats) && weaponType
        && !BG_FindInfinteAmmoForWeapon(i) && !BG_WeaponIsFull(
      i, ent->client->ps.stats, &ent->client->ps.ammo, ent->client->ps.powerups))
    {
      BG_FindAmmoForWeapon(i, &maxAmmo, &maxClips);

      if (buyingEnergyAmmo)
      {
        G_AddEvent(ent, EV_RPTUSE_SOUND, 0);

        if (BG_InventoryContainsUpgrade(UP_BATTPACK, ent->client->ps.stats))
          maxAmmo = (int) ((float) maxAmmo * BATTPACK_MODIFIER);
      }

      BG_PackAmmoArray(i, &ent->client->ps.ammo, ent->client->ps.powerups, maxAmmo, maxClips);

      restoredAmmo = qtrue;
    }
  }

  if (restoredAmmo)
    G_ForceWeaponChange(ent, ent->client->ps.weapon);
}

/*
 ================
 G_BounceProjectile
 ================
 */
void
G_BounceProjectile(vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout)
{
  vec3_t v, newv;
  float dot;

  VectorSubtract(impact, start, v);
  dot = DotProduct(v, dir);
  VectorMA(v, -2 * dot, dir, newv);

  VectorNormalize(newv);
  VectorMA(impact, 8192, newv, endout);
}

/*
 ================
 G_WideTrace

 Trace a bounding box against entities, but not the world
 Also check there is a line of sight between the start and end point
 ================
 */
static void
G_WideTrace(trace_t *tr, gentity_t *ent, float range, float width, gentity_t **target)
{
  vec3_t mins, maxs;
  vec3_t end;

  VectorSet(mins, -width, -width, -width);
  VectorSet(maxs, width, width, width);

  *target = NULL;

  if (!ent->client)
    return;

  // Set aiming directions
  AngleVectors(ent->client->ps.viewangles, forward, right, up);
  CalcMuzzlePoint(ent, forward, right, up, muzzle);
  VectorMA(muzzle, range, forward, end);

  G_UnlaggedOn(ent, muzzle, range);

  // Trace against entities
  trap_Trace(tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_BODY);
  if (tr->entityNum != ENTITYNUM_NONE)
  {
    *target = &g_entities[tr->entityNum];

    // Set range to the trace length plus the width, so that the end of the
    // LOS trace is close to the exterior of the target's bounding box
    range = Distance(muzzle, tr->endpos) + width;
    VectorMA(muzzle, range, forward, end);

    // Trace for line of sight against the world
    trap_Trace(tr, muzzle, NULL, NULL, end, 0, CONTENTS_SOLID);
    if (tr->fraction < 1.0f)
      *target = NULL;
  }

  G_UnlaggedOff();
}

/*
 ======================
 SnapVectorTowards

 Round a vector to integers for more efficient network
 transmission, but make sure that it rounds towards a given point
 rather than blindly truncating.  This prevents it from truncating
 into a wall.
 ======================
 */
void
SnapVectorTowards(vec3_t v, vec3_t to)
{
  int i;

  for(i = 0;i < 3;i++)
  {
    if (to[i] <= v[i])
      v[i] = (int) v[i];
    else
      v[i] = (int) v[i] + 1;
  }
}

/*
 ===============
 meleeAttack
 ===============
 */
void
meleeAttack(gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod)
{
  trace_t tr;
  vec3_t end;
  gentity_t *tent;
  gentity_t *traceEnt;
  vec3_t mins, maxs;

  VectorSet(mins, -width, -width, -width);
  VectorSet(maxs, width, width, width);

  // set aiming directions
  AngleVectors(ent->client->ps.viewangles, forward, right, up);

  CalcMuzzlePoint(ent, forward, right, up, muzzle);

  VectorMA(muzzle, range, forward, end);

  G_UnlaggedOn(ent, muzzle, range);
  trap_Trace(&tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT);
  G_UnlaggedOff();

  if (tr.surfaceFlags & SURF_NOIMPACT)
    return;

  traceEnt = &g_entities[tr.entityNum];

  // send blood impact
  if (traceEnt->takedamage && traceEnt->client)
  {
    tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT);
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  if (traceEnt->takedamage)
    G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod);
}

/*
 ======================================================================

 MACHINEGUN

 ======================================================================
 */

void
bulletFire(gentity_t *ent, float spread, int damage, int mod)
{
  trace_t tr;
  vec3_t end;
  float r;
  float u;
  gentity_t *tent;
  gentity_t *traceEnt;

  r = random() * M_PI * 2.0f;
  u = sin(r) * crandom() * spread * 16;
  r = cos(r) * crandom() * spread * 16;
  VectorMA(muzzle, 8192 * 16, forward, end);
  VectorMA(end, r, right, end);
  VectorMA(end, u, up, end);

  // don't use unlagged if this is not a client (e.g. turret)
  if (ent->client)
  {
    G_UnlaggedOn(ent, muzzle, 8192 * 16);
    trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
    G_UnlaggedOff();
  }
  else
    trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);

  if (tr.surfaceFlags & SURF_NOIMPACT)
    return;

  traceEnt = &g_entities[tr.entityNum];

  // snap the endpos to integers, but nudged towards the line
  SnapVectorTowards(tr.endpos, muzzle);

  // send bullet impact
  if (traceEnt->takedamage && traceEnt->client)
  {
    tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_FLESH);
    tent->s.eventParm = traceEnt->s.number;
  }
  else
  {
    tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_WALL);
    tent->s.eventParm = DirToByte(tr.plane.normal);
  }
  tent->s.otherEntityNum = ent->s.number;

  if (traceEnt->takedamage)
  {
    G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, mod);
  }
}

/*
 ======================================================================

 SHOTGUN

 ======================================================================
 */

// this should match CG_ShotgunPattern

void
ShotgunPattern(vec3_t origin, vec3_t origin2, int seed, gentity_t *ent)
{
  int i;
  float r, u;
  vec3_t end;
  vec3_t forward, right, up;
  trace_t tr;
  gentity_t *traceEnt;

  // derive the right and up vectors from the forward vector, because
  // the client won't have any other information
  VectorNormalize2(origin2, forward);
  PerpendicularVector(right, forward);
  CrossProduct(forward, right, up);

  // generate the "random" spread pattern
  for(i = 0;i < SHOTGUN_PELLETS;i++)
  {
    r = Q_crandom(&seed) * SHOTGUN_SPREAD * 16;
    u = Q_crandom(&seed) * SHOTGUN_SPREAD * 16;
    VectorMA(origin, 8192 * 16, forward, end);
    VectorMA(end, r, right, end);
    VectorMA(end, u, up, end);

    trap_Trace(&tr, origin, NULL, NULL, end, ent->s.number, MASK_SHOT);
    traceEnt = &g_entities[tr.entityNum];

    // send bullet impact
    if (!(tr.surfaceFlags & SURF_NOIMPACT))
    {
      if (traceEnt->takedamage)
        G_Damage(traceEnt, ent, ent, forward, tr.endpos, SHOTGUN_DMG, 0, MOD_SHOTGUN);
    }
  }
}

void
shotgunFire(gentity_t *ent)
{
  gentity_t *tent;

  // send shotgun blast
  tent = G_TempEntity(muzzle, EV_SHOTGUN);
  VectorScale(forward, 4096, tent->s.origin2);
  SnapVector(tent->s.origin2);
  tent->s.eventParm = rand() & 255; // seed for spread pattern
  tent->s.otherEntityNum = ent->s.number;
  G_UnlaggedOn(ent, muzzle, 8192 * 16);
  ShotgunPattern(tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent);
  G_UnlaggedOff();
}

/*
 ======================================================================

 MASS DRIVER

 ======================================================================
 */

void
massDriverFire(gentity_t *ent)
{
  trace_t tr;
  vec3_t end;
  gentity_t *tent;
  gentity_t *traceEnt;

  VectorMA(muzzle, 8192 * 16, forward, end);

  G_UnlaggedOn(ent, muzzle, 8192 * 16);
  trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
  G_UnlaggedOff();

  if (tr.surfaceFlags & SURF_NOIMPACT)
    return;

  traceEnt = &g_entities[tr.entityNum];

  // snap the endpos to integers, but nudged towards the line
  SnapVectorTowards(tr.endpos, muzzle);

  // send impact
  if (traceEnt->takedamage && traceEnt->client)
  {
    tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT);
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }
  else
  {
    tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  if (traceEnt->takedamage)
  {
    G_Damage(traceEnt, ent, ent, forward, tr.endpos, MDRIVER_DMG, 0, MOD_MDRIVER);
  }
}

#define MDRIVER_IMPACTS 10
void
massDriverFire2(gentity_t *ent)
{
  trace_t tr;
  vec3_t start;
  vec3_t end;
  gentity_t *tent;
  gentity_t *traceEnt;
  gentity_t *hits[MDRIVER_IMPACTS];
  int hits_contents[MDRIVER_IMPACTS];
  int count = 0;
  int i;
  qboolean impact = qfalse;

  VectorMA( muzzle, 8192 * 16, forward, end );

  //VectorCopy(muzzle,start); too sloww
  start[0] = muzzle[0];
  start[1] = muzzle[1];
  start[2] = muzzle[2];

  while(count < MDRIVER_IMPACTS)
  {
    trap_Trace(&tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT);
    if (tr.surfaceFlags & SURF_NOIMPACT)
    {
      impact = qfalse;
      break;
    }
    traceEnt = &g_entities[tr.entityNum];

    /* reduce lag
     if( traceEnt->takedamage && traceEnt->client )
     {
     tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
     tent->s.otherEntityNum = traceEnt->s.number;
     tent->s.eventParm = DirToByte( tr.plane.normal );
     tent->s.weapon = ent->s.weapon;
     tent->s.generic1 = ent->s.generic1;
     }*/

    if (traceEnt->takedamage)
    {
      G_Damage(traceEnt, ent, ent, forward, tr.endpos, MDRIVER_DMG, 0, MOD_MDRIVER);
    }
    hits[count] = traceEnt;
    //VectorCopy(tr.endpos,start); tooo sloww
    start[0] = tr.endpos[0];
    start[1] = tr.endpos[1];
    start[2] = tr.endpos[2];
    hits_contents[count] = traceEnt->r.contents;
    traceEnt->r.contents = 0;
    count++;
    impact = qtrue;
  }
  for(i = 0;i < count;i++)
  {
    hits[i]->r.contents = hits_contents[i];
  }
  if (impact)//add miss event to last entity / wall hit
  {
    // snap the endpos to integers, but nudged towards the line
    SnapVectorTowards(tr.endpos, muzzle);
    tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }
}

/*
 ======================================================================

 LOCKBLOB

 ======================================================================
 */

void
lockBlobLauncherFire(gentity_t *ent)
{
  gentity_t *m;

  m = fire_lockblob(ent, muzzle, forward);

  //  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}

void
launcherSecondaryFire(gentity_t *ent)
{
  gentity_t *m;

  m = launch_grenade_secondary(ent, muzzle, forward);
}

void launchAxe(gentity_t *ent)
{
  gentity_t *m;
  BG_RemoveWeaponFromInventory(WP_AXE, ent->client->ps.stats);
  G_ForceWeaponChange(ent, WP_PISTOL);
  m = fire_axe(ent, muzzle,forward);
}
void
launcherFire(gentity_t *ent)
{
  gentity_t *m;

  m = launch_grenade_primary(ent, muzzle, forward);
}
/*
 ======================================================================

 HIVE

 ======================================================================
 */

void
hiveFire(gentity_t *ent)
{
  gentity_t *m;

  m = fire_hive(ent, muzzle, forward);

  //  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}
/*
 ======================================================================

 PULSE RIFLE

 ======================================================================
 */

void
pulseRifleFire(gentity_t *ent)
{
  gentity_t *m;

  m = fire_pulseRifle(ent, muzzle, forward);

}

/*
 ======================================================================

 FLAME THROWER

 ======================================================================
 */

void
flamerFire(gentity_t *ent)
{
  gentity_t *m;

  m = fire_flamer(ent, muzzle, forward);
}

/*
 ======================================================================

 GRENADE

 ======================================================================
 */

void
throwGrenade(gentity_t *ent)
{
  gentity_t *m;

  m = launch_grenade(ent, muzzle, forward);
}

////////////////////////////////////////////////////////////////////////////
// MINE
////////////////////////////////////////////////////////////////////////////
void
plantMine(gentity_t *ent)
{
  gentity_t *m;
  if (ent->numMines > 1)
  {
    trap_SendServerCommand(ent - g_entities, "print \"You cannot drop more than 2 mines.\n\"");
    return;
  }
  ent->numMines++;
  m = plant_mine(ent, muzzle, forward);
}

/*
 ======================================================================

 BOMB

 ======================================================================
 */

void
throwBomb(gentity_t *ent)
{
  // gentity_t *m;

  // m = launch_bomb( ent, muzzle, forward );
}

/*
 ======================================================================

 LAS GUN

 ======================================================================
 */

/*
 ===============
 lasGunFire
 ===============
 */
void
lasGunFire(gentity_t *ent)
{
  trace_t tr;
  vec3_t end;

  gentity_t *traceEnt, *tent;
  int damage, i, passent;

  damage = LASGUN_DAMAGE;

  passent = ent->s.number;

  VectorMA(muzzle, LASGUN_RANGE, forward, end);

  trap_Trace(&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT);

  if (tr.entityNum == ENTITYNUM_NONE)
  {
    return;
  }

  traceEnt = &g_entities[tr.entityNum];

  if (traceEnt->takedamage)
  {
    G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_LASERGUN);
  }

  // send impact
  if (traceEnt->takedamage && traceEnt->client)
  {
    tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT);
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }
  else
  {
    tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }
}

/*
 ======================================================================

 PAIN SAW

 ======================================================================
 */

void
painSawFire(gentity_t *ent)
{
  trace_t tr;
  vec3_t end;
  gentity_t *tent;
  gentity_t *traceEnt;

  // set aiming directions
  AngleVectors(ent->client->ps.viewangles, forward, right, up);

  CalcMuzzlePoint(ent, forward, right, up, muzzle);

  VectorMA(muzzle, PAINSAW_RANGE, forward, end);

  G_UnlaggedOn(ent, muzzle, PAINSAW_RANGE);
  trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
  G_UnlaggedOff();

  if (tr.surfaceFlags & SURF_NOIMPACT)
    return;

  traceEnt = &g_entities[tr.entityNum];

  // send blood impact
  if (traceEnt->takedamage)
  {
    vec3_t temp;

    //hack to get the particle system to line up with the weapon
    VectorCopy(tr.endpos, temp);
    temp[2] -= 10.0f;

    if (traceEnt->client)
    {
      tent = G_TempEntity(temp, EV_MISSILE_HIT);
      tent->s.otherEntityNum = traceEnt->s.number;
    }
    else
      tent = G_TempEntity(temp, EV_MISSILE_MISS);

    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  //if (traceEnt->takedamage)
  //  G_Damage(
  //    traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW);
}

/*
 ======================================================================

 LUCIFER CANNON

 ======================================================================
 */

/*
 ===============
 LCChargeFire
 ===============
 */
void
LCChargeFire(gentity_t *ent, qboolean secondary)
{
  gentity_t *m;

  if (secondary)
  {
    m
        = fire_luciferCannon(
          ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE, LCANNON_SECONDARY_RADIUS);
    ent->client->ps.weaponTime = LCANNON_REPEAT;
  }
  else
  {
    m = fire_luciferCannon(ent, muzzle, forward, ent->client->ps.stats[STAT_MISC], LCANNON_RADIUS);
    ent->client->ps.weaponTime = LCANNON_CHARGEREPEAT;
  }

  ent->client->ps.stats[STAT_MISC] = 0;
}

/*
 ======================================================================

 TESLA GENERATOR

 ======================================================================
 */

void
teslaFire(gentity_t *ent)
{
  trace_t tr;
  vec3_t end;
  gentity_t *traceEnt, *tent;
  int team;

  VectorMA(muzzle, TESLAGEN_RANGE, forward, end);

  trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);

  if (tr.entityNum == ENTITYNUM_NONE)
    return;

  traceEnt = &g_entities[tr.entityNum];

  if (ent->biteam == BIT_ALIENS)
  {
    team = PTE_ALIENS;
  }
  if (ent->biteam == BIT_HUMANS)
  {
    team = PTE_HUMANS;
  }

  if (!traceEnt->client && !(traceEnt->s.eType == ET_BUILDABLE))
    return;

  if ((traceEnt->s.eType == ET_BUILDABLE) && (traceEnt->biteam == ent->biteam))
    return;

  if (traceEnt->client && (traceEnt->client->ps.stats[STAT_PTEAM] == team))
    return;

  /*if( !traceEnt->client )
   return;*/

  /*if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
   return;*/

  //so the client side knows
  ent->s.eFlags |= EF_FIRING;

  if (traceEnt->takedamage)
  {
    G_Damage(traceEnt, ent, ent, forward, tr.endpos, TESLAGEN_DMG, 0, MOD_TESLAGEN);
  }

  // snap the endpos to integers to save net bandwidth, but nudged towards the line
  SnapVectorTowards(tr.endpos, muzzle);

  // send railgun beam effect
  tent = G_TempEntity(tr.endpos, EV_TESLATRAIL);

  VectorCopy(muzzle, tent->s.origin2);

  tent->s.generic1 = ent->s.number; //src
  tent->s.clientNum = traceEnt->s.number; //dest

  // move origin a bit to come closer to the drawn gun muzzle
  VectorMA(tent->s.origin2, 28, up, tent->s.origin2);
}

/*
 ======================================================================

 BUILD GUN

 ======================================================================
 */

/*
 ===============
 cancelBuildFire
 ===============
 */
void
cancelBuildFire(gentity_t *ent)
{
  vec3_t forward, end;
  trace_t tr;
  gentity_t *traceEnt;
  int bHealth;

  if (ent->client->ps.stats[STAT_BUILDABLE] != BA_NONE)
  {
    ent->client->ps.stats[STAT_BUILDABLE] = BA_NONE;
    return;
  }

  //Reapir budy.
  if (ent->client->ps.stats[STAT_PTEAM] == PTE_HUMANS && g_survival.integer)
  {
    return;
//    trap_SendServerCommand(
//              ent - g_entities, "print \"^2Primary Fire.\n\"");
    AngleVectors(ent->client->ps.viewangles, forward, NULL, NULL);
    VectorMA(ent->client->ps.origin, 200, forward, end);

    trap_Trace(&tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID);
    traceEnt = &g_entities[tr.entityNum];

    if (!traceEnt)
      return;
    if (!traceEnt->client)
      return;

    if (tr.fraction < 1.0 && (traceEnt->client->ps.stats[STAT_PTEAM] == PTE_HUMANS)
        && (ent->client->ps.weapon >= WP_HBUILD2) && (ent->client->ps.weapon <= WP_HBUILD)
        && traceEnt->health > 0 && traceEnt->client->ps.stats[STAT_HEALTH] > 0
        && traceEnt->client->sess.sessionTeam != TEAM_SPECTATOR)
    {
      if (ent->client->ps.stats[STAT_MISC] > 0)
      {
        G_AddEvent(ent, EV_BUILD_DELAY, ent->client->ps.clientNum);
        return;
      }

      if (ent->client->ps.persistant[PERS_UNUSED] < 1)
      {
        G_AddEvent(ent, EV_BUILD_DELAY, ent->client->ps.clientNum);
        trap_SendServerCommand(
          ent - g_entities, "print \"^2You need to pickup a medical supply.\n\"");
        return;
      }

      if (traceEnt->health < 100)
      {
        traceEnt->health = traceEnt->client->ps.stats[STAT_HEALTH]
            = traceEnt->client->ps.stats[STAT_MAX_HEALTH];
        G_AddEvent(traceEnt, EV_MEDKIT_USED, 0);
        ent->client->ps.persistant[PERS_UNUSED] -= 1;
        ent->client->ps.persistant[PERS_SCORE] += 10;
      }

      ent->client->pers.statscounters.repairspoisons++;
      level.humanStatsCounters.repairspoisons++;

      if (traceEnt->health > 100)
        traceEnt->health = 100;

      if (traceEnt->health == 100)
        G_AddEvent(ent, EV_BUILD_REPAIRED, 0);
      else
      {
        G_AddEvent(ent, EV_BUILD_REPAIR, 0);
      }
    }
  }
  if (g_survival.integer)
    return;

  //repair buildable
  if (ent->client->ps.stats[STAT_PTEAM] == PTE_HUMANS || ent->client->ps.stats[STAT_PTEAM]
      == PTE_ALIENS)
  {
    AngleVectors(ent->client->ps.viewangles, forward, NULL, NULL);
    VectorMA(ent->client->ps.origin, 100, forward, end);

    trap_Trace(&tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID);
    traceEnt = &g_entities[tr.entityNum];

    if (tr.fraction < 1.0 && (traceEnt->s.eType == ET_BUILDABLE) && (traceEnt->biteam
        == ent->client->ps.stats[STAT_PTEAM]) && ((ent->client->ps.weapon >= WP_HBUILD2)
        && (ent->client->ps.weapon <= WP_HBUILD)) && traceEnt->spawned && traceEnt->health > 0)
    {
      if (ent->client->ps.stats[STAT_MISC] > 0)
      {
        G_AddEvent(ent, EV_BUILD_DELAY, ent->client->ps.clientNum);
        return;
      }

      bHealth = BG_FindHealthForBuildable(traceEnt->s.modelindex);

      traceEnt->health += HBUILD_HEALRATE;
      ent->client->pers.statscounters.repairspoisons++;
      level.humanStatsCounters.repairspoisons++;

      if (traceEnt->health > bHealth)
        traceEnt->health = bHealth;

      if (traceEnt->health == bHealth)
        G_AddEvent(ent, EV_BUILD_REPAIRED, 0);
      else
        G_AddEvent(ent, EV_BUILD_REPAIR, 0);
    }
  }
  else if (ent->client->ps.weapon == WP_ABUILD2)
    meleeAttack(ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH, ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW); //melee attack for alien builder
}

/*
 ===============
 buildFire
 ===============
 */
void
buildFire(gentity_t *ent, dynMenu_t menu)
{
  int biteam;

  //If survival shot the dome.
  if(g_survival.integer)
  {
    gentity_t      *m;

    if (ent->client->ps.stats[STAT_MISC] > 0)
    {
      G_AddEvent(ent, EV_BUILD_DELAY, ent->client->ps.clientNum);
      return;
    }

    if (ent->client->ps.persistant[PERS_UNUSED] < 1)
    {
      G_AddEvent(ent, EV_BUILD_DELAY, ent->client->ps.clientNum);
      trap_SendServerCommand(ent - g_entities, "print \"^2You need to pickup a medical supply.\n\"");
      return;
    }
    if (ent->numDomes > 0)
    {
      trap_SendServerCommand(ent - g_entities, "print \"You cannot drop more than 1 Dome.\n\"");
      return;
    }
    ent->numDomes++;

    ent->client->ps.persistant[PERS_UNUSED] -= 1; //Medkit.
    ent->client->ps.persistant[PERS_SCORE] += 10; //Incrase score.

    m = fire_dome(ent, muzzle, forward);
    return;
  }

  if (ent->client->ps.stats[STAT_PTEAM] == PTE_HUMANS)
  {
    biteam = BIT_HUMANS;
  }
  else
  {
    biteam = BIT_ALIENS;
  }
  if ((ent->client->ps.stats[STAT_BUILDABLE] & ~SB_VALID_TOGGLEBIT) > BA_NONE)
  {
    if (ent->client->ps.stats[STAT_MISC] > 0)
    {
      G_AddEvent(ent, EV_BUILD_DELAY, ent->client->ps.clientNum);
      return;
    }

    if (G_BuildIfValid(ent, ent->client->ps.stats[STAT_BUILDABLE] & ~SB_VALID_TOGGLEBIT))
    {
      if (g_cheats.integer)
      {
        ent->client->ps.stats[STAT_MISC] = 0;
      } /*else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) )
       {
       ent->client->ps.stats[ STAT_MISC ] +=
       BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
       }*/
      else if ((ent->client->ps.stats[STAT_PTEAM] == PTE_HUMANS
          || ent->client->ps.stats[STAT_PTEAM] == PTE_ALIENS) && !G_IsPowered(muzzle, biteam)
          && (ent->client->ps.stats[STAT_BUILDABLE] & ~SB_VALID_TOGGLEBIT) != BA_H_REPEATER) //hack
      {
        ent->client->ps.stats[STAT_MISC] += BG_FindBuildDelayForWeapon(ent->s.weapon) * 2;
      }
      else
        ent->client->ps.stats[STAT_MISC] += BG_FindBuildDelayForWeapon(ent->s.weapon);

      ent->client->ps.stats[STAT_BUILDABLE] = BA_NONE;

      // don't want it bigger than 32k
      if (ent->client->ps.stats[STAT_MISC] > 30000)
        ent->client->ps.stats[STAT_MISC] = 30000;
    }
    return;
  }

  G_TriggerMenu(ent->client->ps.clientNum, menu);
}

void
slowBlobFire(gentity_t *ent)
{
  gentity_t *m;

  m = fire_slowBlob(ent, muzzle, forward);

  //  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}

/*
 ======================================================================

 LEVEL0

 ======================================================================
 */

/*
 ===============
 CheckVenomAttack
 ===============
 */
qboolean
CheckVenomAttack(gentity_t *ent)
{
  trace_t tr;
  vec3_t end;
  gentity_t *tent;
  gentity_t *traceEnt;
  vec3_t mins, maxs;
  int damage = LEVEL0_BITE_DMG;

  VectorSet(mins, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH);
  VectorSet(maxs, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH);

  // set aiming directions
  AngleVectors(ent->client->ps.viewangles, forward, right, up);

  CalcMuzzlePoint(ent, forward, right, up, muzzle);

  VectorMA(muzzle, LEVEL0_BITE_RANGE, forward, end);

  G_UnlaggedOn(ent, muzzle, LEVEL0_BITE_RANGE);
  trap_Trace(&tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT);
  G_UnlaggedOff();

  if (tr.surfaceFlags & SURF_NOIMPACT)
    return qfalse;

  traceEnt = &g_entities[tr.entityNum];

  if (!traceEnt->takedamage)
    return qfalse;

  if (!traceEnt->client && !traceEnt->s.eType == ET_BUILDABLE)
    return qfalse;

  //allow bites to work against defensive buildables only
  if (traceEnt->s.eType == ET_BUILDABLE)
  {
    if (traceEnt->s.modelindex != BA_H_MGTURRET && traceEnt->s.modelindex != BA_H_TESLAGEN)
      return qfalse;

    //hackery
    damage *= 0.5f;
  }

  if (traceEnt->client)
  {
    if (traceEnt->client->ps.stats[STAT_PTEAM] == PTE_ALIENS)
      return qfalse;
    if (traceEnt->client->ps.stats[STAT_HEALTH] <= 0)
      return qfalse;
  }

  // send blood impact
  if (traceEnt->takedamage && traceEnt->client)
  {
    tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT);
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE);

  return qtrue;
}

/*
 ======================================================================

 LEVEL1

 ======================================================================
 */

/*
 ===============
 CheckGrabAttack
 ===============
 */
void
CheckGrabAttack(gentity_t *ent)
{
  trace_t tr;
  vec3_t end, dir;
  gentity_t *traceEnt;

  // set aiming directions
  AngleVectors(ent->client->ps.viewangles, forward, right, up);

  CalcMuzzlePoint(ent, forward, right, up, muzzle);

  VectorMA(muzzle, LEVEL1_GRAB_RANGE, forward, end);

  trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
  if (tr.surfaceFlags & SURF_NOIMPACT)
    return;

  traceEnt = &g_entities[tr.entityNum];

  if (!traceEnt->takedamage)
    return;

  if (traceEnt->client)
  {
    if (traceEnt->client->ps.stats[STAT_PTEAM] == PTE_ALIENS)
      return;

    if (traceEnt->client->ps.stats[STAT_HEALTH] <= 0)
      return;

    if (!(traceEnt->client->ps.stats[STAT_STATE] & SS_GRABBED))
    {
      AngleVectors(traceEnt->client->ps.viewangles, dir, NULL, NULL);
      traceEnt->client->ps.stats[STAT_VIEWLOCK] = DirToByte(dir);

      //event for client side grab effect
      G_AddPredictableEvent(ent, EV_LEV1_GRAB, 0);
    }

    traceEnt->client->ps.stats[STAT_STATE] |= SS_GRABBED;

    if (ent->client->ps.weapon == WP_ALEVEL1)
      traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_TIME;
    else if (ent->client->ps.weapon == WP_ALEVEL1_UPG)
      traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_U_TIME;
  }
  else if (traceEnt->s.eType == ET_BUILDABLE && traceEnt->s.modelindex == BA_H_MGTURRET)
  {
    if (!traceEnt->lev1Grabbed)
      G_AddPredictableEvent(ent, EV_LEV1_GRAB, 0);

    traceEnt->lev1Grabbed = qtrue;
    traceEnt->lev1GrabTime = level.time;
  }
}

/*
 ===============
 poisonCloud
 ===============
 */
void
poisonCloud(gentity_t *ent)
{
  int entityList[MAX_GENTITIES];
  vec3_t range =
  { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE };
  vec3_t mins, maxs;
  int i, num;
  gentity_t *humanPlayer;
  trace_t tr;

  VectorAdd(ent->client->ps.origin, range, maxs);
  VectorSubtract(ent->client->ps.origin, range, mins);

  G_UnlaggedOn(ent, ent->client->ps.origin, LEVEL1_PCLOUD_RANGE);
  num = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES);
  for(i = 0;i < num;i++)
  {
    humanPlayer = &g_entities[entityList[i]];

    if (humanPlayer->client && humanPlayer->client->ps.stats[STAT_PTEAM] == PTE_HUMANS)
    {
      if (BG_InventoryContainsUpgrade(UP_LIGHTARMOUR, humanPlayer->client->ps.stats))
        continue;

      /*if (BG_InventoryContainsUpgrade(UP_BATTLESUIT, humanPlayer->client->ps.stats))
       continue;*/

      trap_Trace(&tr, muzzle, NULL, NULL, humanPlayer->s.origin, humanPlayer->s.number, MASK_SHOT);

      //can't see target from here
      if (tr.entityNum == ENTITYNUM_WORLD)
        continue;

      if (!(humanPlayer->client->ps.stats[STAT_STATE] & SS_POISONCLOUDED))
      {
        humanPlayer->client->ps.stats[STAT_STATE] |= SS_POISONCLOUDED;
        humanPlayer->client->lastPoisonCloudedTime = level.time;
        humanPlayer->client->lastPoisonCloudedClient = ent;
        trap_SendServerCommand(humanPlayer->client->ps.clientNum, "poisoncloud");
      }
    }
  }
  G_UnlaggedOff();
}

/*
 ======================================================================

 LEVEL2

 ======================================================================
 */

#define MAX_ZAPS  64

static zap_t zaps[MAX_CLIENTS];

/*
 ===============
 G_FindNewZapTarget
 ===============
 */
static gentity_t *
G_FindNewZapTarget(gentity_t *ent)
{
  int entityList[MAX_GENTITIES];
  vec3_t range =
  { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE };
  vec3_t mins, maxs;
  int i, j, k, num;
  gentity_t *enemy;
  trace_t tr;

  VectorScale(range, 1.0f / M_ROOT3, range);
  VectorAdd(ent->s.origin, range, maxs);
  VectorSubtract(ent->s.origin, range, mins);

  num = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES);

  for(i = 0;i < num;i++)
  {
    enemy = &g_entities[entityList[i]];

    if (((enemy->client && enemy->client->ps.stats[STAT_PTEAM] == PTE_HUMANS) || (enemy->s.eType
        == ET_BUILDABLE && BG_FindTeamForBuildable(enemy->s.modelindex) == BIT_HUMANS))
        && enemy->health > 0)
    {
      qboolean foundOldTarget = qfalse;

      trap_Trace(&tr, muzzle, NULL, NULL, enemy->s.origin, ent->s.number, MASK_SHOT);

      //can't see target from here
      if (tr.entityNum == ENTITYNUM_WORLD)
        continue;

      for(j = 0;j < MAX_ZAPS;j++)
      {
        zap_t *zap = &zaps[j];

        for(k = 0;k < zap->numTargets;k++)
        {
          if (zap->targets[k] == enemy)
          {
            foundOldTarget = qtrue;
            break;
          }
        }

        if (foundOldTarget)
          break;
      }

      // enemy is already targetted
      if (foundOldTarget)
        continue;

      return enemy;
    }
  }

  return NULL;
}

/*
 ===============
 G_UpdateZapEffect
 ===============
 */
static void
G_UpdateZapEffect(zap_t *zap)
{
  int j;
  gentity_t *effect = zap->effectChannel;

  effect->s.eType = ET_LEV2_ZAP_CHAIN;
  effect->classname = "lev2zapchain";
  G_SetOrigin(effect, zap->creator->s.origin);
  effect->s.powerups = zap->creator->s.number;

  effect->s.time = effect->s.time2 = effect->s.constantLight = -1;

  for(j = 0;j < zap->numTargets;j++)
  {
    int number = zap->targets[j]->s.number;

    switch(j)
    {
      case 0:
        effect->s.time = number;
        break;
      case 1:
        effect->s.time2 = number;
        break;
      case 2:
        effect->s.constantLight = number;
        break;
      default:
        break;
    }
  }

  trap_LinkEntity(effect);
}

/*
 ===============
 G_CreateNewZap
 ===============
 */
static void
G_CreateNewZap(gentity_t *creator, gentity_t *target)
{
  int i, j;
  zap_t *zap;

  for(i = 0;i < MAX_ZAPS;i++)
  {
    zap = &zaps[i];

    if (!zap->used)
    {
      zap->used = qtrue;

      zap->timeToLive = LEVEL2_AREAZAP_TIME;
      zap->damageUsed = 0;

      zap->creator = creator;

      zap->targets[0] = target;
      zap->numTargets = 1;

      for(j = 1;j < MAX_ZAP_TARGETS && zap->targets[j - 1];j++)
      {
        zap->targets[j] = G_FindNewZapTarget(zap->targets[j - 1]);

        if (zap->targets[j])
          zap->numTargets++;
      }

      zap->effectChannel = G_Spawn();
      G_UpdateZapEffect(zap);

      return;
    }
  }
}

/*
 ===============
 G_UpdateZaps
 ===============
 */
void
G_UpdateZaps(int msec)
{
  int i, j;
  zap_t *zap;
  int damage;

  for(i = 0;i < MAX_ZAPS;i++)
  {
    zap = &zaps[i];

    if (zap->used)
    {
      //check each target is valid
      for(j = 0;j < zap->numTargets;j++)
      {
        gentity_t *source;
        gentity_t *target = zap->targets[j];

        if (j == 0)
          source = zap->creator;
        else
          source = zap->targets[j - 1];

        if (target->health <= 0 || !target->inuse || //early out
            Distance(source->s.origin, target->s.origin) > LEVEL2_AREAZAP_RANGE)
        {
          target = zap->targets[j] = G_FindNewZapTarget(source);

          //couldn't find a target, so forget about the rest of the chain
          if (!target)
            zap->numTargets = j;
        }
      }

      if (zap->numTargets)
      {
        for(j = 0;j < zap->numTargets;j++)
        {
          gentity_t *source;
          gentity_t *target = zap->targets[j];
          float r = 1.0f / zap->numTargets;
          float damageFraction = 2 * r - 2 * j * r * r - r * r;
          vec3_t forward;

          if (j == 0)
            source = zap->creator;
          else
            source = zap->targets[j - 1];

          damage = ceil(((float) msec / LEVEL2_AREAZAP_TIME) * LEVEL2_AREAZAP_DMG * damageFraction);

          // don't let a high msec value inflate the total damage
          if (damage + zap->damageUsed > LEVEL2_AREAZAP_DMG)
            damage = LEVEL2_AREAZAP_DMG - zap->damageUsed;

          VectorSubtract(target->s.origin, source->s.origin, forward);
          VectorNormalize(forward);

          //do the damage
          if (damage)
          {
//            G_Damage(
//              target, source, zap->creator, forward, target->s.origin, damage, DAMAGE_NO_KNOCKBACK
//                  | DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP);
//            zap->damageUsed += damage;
          }
        }
      }

      G_UpdateZapEffect(zap);

      zap->timeToLive -= msec;

      if (zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0)
      {
        zap->used = qfalse;
        G_FreeEntity(zap->effectChannel);
      }
    }
  }
}

/*
 ===============
 areaZapFire
 ===============
 */
void
areaZapFire(gentity_t *ent)
{
  trace_t tr;
  vec3_t end;
  gentity_t *traceEnt;
  vec3_t mins, maxs;

  VectorSet(mins, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH);
  VectorSet(maxs, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH);

  // set aiming directions
  AngleVectors(ent->client->ps.viewangles, forward, right, up);

  CalcMuzzlePoint(ent, forward, right, up, muzzle);

  VectorMA(muzzle, LEVEL2_AREAZAP_RANGE, forward, end);

  G_UnlaggedOn(ent, muzzle, LEVEL2_AREAZAP_RANGE);
  trap_Trace(&tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT);
  G_UnlaggedOff();

  if (tr.surfaceFlags & SURF_NOIMPACT)
    return;

  traceEnt = &g_entities[tr.entityNum];

  if (((traceEnt->client && traceEnt->client->ps.stats[STAT_PTEAM] == PTE_HUMANS)
      || (traceEnt->s.eType == ET_BUILDABLE && BG_FindTeamForBuildable(traceEnt->s.modelindex)
          == BIT_HUMANS)) && traceEnt->health > 0)
  {
    G_CreateNewZap(ent, traceEnt);
  }
}

/*
 ======================================================================

 LEVEL3

 ======================================================================
 */

/*
 ===============
 CheckPounceAttack
 ===============
 */
qboolean
CheckPounceAttack(gentity_t *ent)
{
  trace_t tr;
  gentity_t *tent;
  gentity_t *traceEnt;
  int damage;

  if (ent->client->ps.groundEntityNum != ENTITYNUM_NONE)
  {
    ent->client->allowedToPounce = qfalse;
    ent->client->pmext.pouncePayload = 0;
  }

  if (!ent->client->allowedToPounce)
    return qfalse;

  if (ent->client->ps.weaponTime)
    return qfalse;

  G_WideTrace(&tr, ent, LEVEL3_POUNCE_RANGE, LEVEL3_POUNCE_WIDTH, &traceEnt);

  if (traceEnt == NULL)
    return qfalse;

  // send blood impact
  if (traceEnt->takedamage && traceEnt->client)
  {
    tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT);
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte(tr.plane.normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  if (!traceEnt->takedamage)
    return qfalse;

  damage = (int) (((float) ent->client->pmext.pouncePayload / (float) LEVEL3_POUNCE_SPEED)
      * LEVEL3_POUNCE_DMG);

  ent->client->pmext.pouncePayload = 0;

//  G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK
//      | DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE);

  ent->client->allowedToPounce = qfalse;

  return qtrue;
}

void
bounceBallFire(gentity_t *ent)
{
  gentity_t *m;

  m = fire_bounceBall(ent, muzzle, forward);

  //  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}

/*
 ======================================================================

 LEVEL4

 ======================================================================
 */

/*
 ===============
 ChargeAttack
 ===============
 */
void
ChargeAttack(gentity_t *ent, gentity_t *victim)
{
  gentity_t *tent;
  int damage;
  vec3_t forward, normal;

  if (level.time < victim->chargeRepeat)
    return;

  victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT;

  VectorSubtract(victim->s.origin, ent->s.origin, forward);
  VectorNormalize(forward);
  VectorNegate(forward, normal);

  if (victim->client)
  {
    tent = G_TempEntity(victim->s.origin, EV_MISSILE_HIT);
    tent->s.otherEntityNum = victim->s.number;
    tent->s.eventParm = DirToByte(normal);
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  if (!victim->takedamage)
    return;

  damage = (int) (((float) ent->client->ps.stats[STAT_MISC] / (float) LEVEL4_CHARGE_TIME)
      * LEVEL4_CHARGE_DMG);

//  G_Damage(victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE);
}


void fireRocket(gentity_t *ent)
{
  gentity_t      *m;

  m = fire_rocket(ent, muzzle, forward);
}

//======================================================================

/*
 ===============
 CalcMuzzlePoint

 set muzzle location relative to pivoting eye
 ===============
 */
void
CalcMuzzlePoint(gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint)
{

  VectorCopy(ent->s.pos.trBase, muzzlePoint);
  muzzlePoint[2] += ent->client->ps.viewheight;
  VectorMA(muzzlePoint, 1, forward, muzzlePoint);
  VectorMA(muzzlePoint, 1, right, muzzlePoint);

  // snap to integer coordinates for more efficient network bandwidth usage
  SnapVector(muzzlePoint);
}

/*
 ===============
 FireWeapon3
 ===============
 */
void
FireWeapon3(gentity_t *ent)
{
  if (ent->client)
  {
    // set aiming directions
    AngleVectors(ent->client->ps.viewangles, forward, right, up);
    CalcMuzzlePoint(ent, forward, right, up, muzzle);
  }
  else
  {
    AngleVectors(ent->s.angles2, forward, right, up);
    VectorCopy(ent->s.pos.trBase, muzzle);
  }

  // fire the specific weapon
  switch(ent->s.weapon)
  {
    case WP_ALEVEL3_UPG:
      bounceBallFire(ent);
      break;

    case WP_ABUILD2:
      slowBlobFire(ent);
      break;

    default:
      break;
  }
}

/*
 ===============
 FireWeapon2
 ===============
 */
void
FireWeapon2(gentity_t *ent)
{
  if (ent->client)
  {
    // set aiming directions
    AngleVectors(ent->client->ps.viewangles, forward, right, up);
    CalcMuzzlePoint(ent, forward, right, up, muzzle);
  }
  else
  {
    AngleVectors(ent->s.angles2, forward, right, up);
    VectorCopy(ent->s.pos.trBase, muzzle);
  }

  // fire the specific weapon
  switch(ent->s.weapon)
  {
    case WP_ALEVEL1_UPG:
      poisonCloud(ent);
      break;
    case WP_ALEVEL2_UPG:
      areaZapFire(ent);
      break;

    case WP_LAUNCHER:
      launcherSecondaryFire(ent);
      break;

    case WP_AXE:
      launchAxe(ent);
      break;

    case WP_ABUILD:
    case WP_ABUILD2:
    case WP_HBUILD:
    case WP_HBUILD2:
      cancelBuildFire(ent);
      break;
    default:
      break;
  }
}

/*
 ===============
 FireWeapon
 ===============
 */
void
FireWeapon(gentity_t *ent)
{
  if (ent->client)
  {
    // set aiming directions
    AngleVectors(ent->client->ps.viewangles, forward, right, up);
    CalcMuzzlePoint(ent, forward, right, up, muzzle);
  }
  else
  {
    AngleVectors(ent->turretAim, forward, right, up);
    VectorCopy(ent->s.pos.trBase, muzzle);
  }

  // fire the specific weapon
  switch(ent->s.weapon)
  {
    case WP_MACHINEGUN:
      bulletFire(ent, RIFLE_SPREAD, RIFLE_DMG, MOD_MACHINEGUN);
      break;
    case WP_SHOTGUN:
      shotgunFire(ent);
      break;
    case WP_CHAINGUN:
      bulletFire(ent, CHAINGUN_SPREAD, CHAINGUN_DMG, MOD_CHAINGUN);
      break;
    case WP_MASS_DRIVER:
      massDriverFire2(ent);
      break;
    case WP_LAS_GUN:
      lasGunFire(ent);
      break;
    case WP_GRENADE:
      throwGrenade(ent);
      break;
    case WP_MINE:
      plantMine(ent);
      break;
    case WP_ZOMBIE_BITE:
      meleeAttack(ent, ZOMBIE_RANGE*2, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG/3, MOD_ZOMBIE_BITE);
      break;
    case WP_LAUNCHER:
      launcherFire(ent);
      break;


    case WP_TESLAGEN:
      teslaFire(ent);
      break;
    case WP_MGTURRET:
      bulletFire(ent, MGTURRET_SPREAD, MGTURRET_DMG, MOD_MGTURRET);
      break;

    case WP_HBUILD:
    case WP_HBUILD2:
      buildFire(ent, MN_H_BUILD);
      break;
    //New Weapons
    case WP_AXE:
      meleeAttack(ent, AXE_RANGE, AXE_WIDTH, AXE_DAMAGE, MOD_AXE);
      break;
    case WP_PISTOL:
      bulletFire(ent,0,PISTOL_DAMAGE, MOD_PISTOL);
      break;
    case WP_ROCKET_LAUNCHER:
      fireRocket(ent);
      break;
    default:
      break;
  }
}

